#include "../include/kasm.h"
#include "../include/defs.h"
#include "../include/grubInfo.h"
#include "../include/kc.h"
#include "../include/shell.h"
#include "../include/sleep.h"
#include "../include/keyboard.h"
#include "../include/exceptions.h"
#include "../include/page.h"
#include "../include/fs.h"
#include "../include/shell.h"
#include "../include/fifo.h"
#include "../include/game.h"

#define NTERMS      6

/* Se definen las interrupciones */
#define IRQ0	0xFE
#define IRQ1	0xFD
#define IRQ3	0xF7
#define IRQ4	0xEF

#define EXEC_CODE   1
#define KILL_CODE   2
#define RENICE_CODE 3 //to implement
#define TOP_CODE    4
#define WRITE_CODE  5
#define GCNB_CODE   6
#define GC_CODE     7
#define READ_CODE   8
#define MALLOC_CODE 9
#define LOCK_CODE   12
#define UNLOCK_CODE 13

//test
DESCR_INT idt[0x95];
IDTR idtr; /* IDTR */

int tickpos = 0;

unsigned long *page_directory = (unsigned long *) 0x400000;
unsigned long *page_table = (unsigned long *) 0x401000;
// the page table comes right after the page directory

process_t procesos[MAXPROCESOS];
process_t init;
peticion peticionesDormir[MAXPROCESOS];
TTY terminales[NTERMS];
unsigned char *keyboardBuffer = (char *) 0;
unsigned char *writeBuffer = (char *) 0;
unsigned char *readBuffer = (char *) 0;

char *backup;
int maxmem;

int terminalActual = 0;
int pidActual = 0;
int proxPid = 1;
int maxmem;

inode ** fs;

int idle(int argc, char **argv) {
	while (1) {
		asm volatile ("hlt");
	}
}

void int_08() {
	_Sti();
}

void int_81(char *nombre, int(*proceso)(int argc, char **argv),
		int enBackground) {
	exec(nombre, proceso, enBackground);
}

char *
getVideo(void) {
	process_t *temp;
	temp = GetProcessByPid(pidActual);

	if (temp->terminal == terminalActual)
		return (char *) 0xb8000;
	else
		return terminales[temp->terminal].terminal;
}

char *
getBuffer(void) {
	return keyboardBuffer;
}

void int_09(int scanCode) {
	int proxTerm, i;
	int code = scanCode & 0x00FF;
	char ascii = getAscii(code);

	if (canBeShowed(code)) {
		/* Si el caracter se puede mostrar, se escribe en el buffer */
		for (i = 0; i < MAXPROCESOS; i++) {
			if (procesos[i].terminal == terminalActual
					&& !procesos[i].background && procesos[i].bloqueado
					== WAITING_INPUT) {
				procesos[i].bloqueado = 0;
			}
		}
		addKeyToBuffer(ascii);
	} else if (isFmake(code)) {

		proxTerm = ascii - F1;

		if (proxTerm >= NTERMS)
			return;

		if (proxTerm == terminalActual)
			return;
		else {

			desbloqueaProceso(proxTerm + 1);
			if (!estaBloqueado(terminalActual + 1))
				bloqueaProceso(terminalActual + 1, 1);

			for (i = 0; i < FILAS * COLS * 2; i++) {
				backup[i] = terminales[terminalActual].terminal[i];
			}

			terminales[terminalActual].terminal = backup;
			terminales[terminalActual].posicion = tickpos;
			terminales[terminalActual].readBuf = readBuffer;
			terminales[terminalActual].writeBuf = writeBuffer;

			tickpos = terminales[proxTerm].posicion;
			backup = terminales[proxTerm].terminal;
			terminales[proxTerm].terminal = (char *) 0xb8000;
			update_cursor(tickpos / 160, (tickpos % 160) / 2);

			for (i = 0; i < FILAS * COLS * 2; i++) {
				terminales[proxTerm].terminal[i] = backup[i];
			}

			keyboardBuffer = terminales[proxTerm].buffer;
			writeBuffer = terminales[proxTerm].writeBuf;
			readBuffer = terminales[proxTerm].readBuf;
			terminalActual = proxTerm;
		}
	}
}

void escribir(char *donde, char *desde, int cant) {
	int j;
	int offset;

	unsigned int *pos;
	process_t *temp;

	temp = GetProcessByPid(pidActual);
	if (temp->terminal == terminalActual) {
		pos = &tickpos;
	} else {
		pos = &terminales[temp->terminal].posicion;
	}

	if (*pos >= COLS * FILAS * 2) {
		scroll(donde, COLS, FILAS);

		/* llegue al final de la pantalla */
		for (j = COLS * FILAS * 2; j >= COLS * (FILAS - 1) * 2; j -= 2)
			donde[j] = 0;

		*pos = COLS * (FILAS - 1) * 2;
	}

	for (j = 0; j < cant; j++) {
		switch (desde[j]) {
		case '\n':
			/* hay que mover el cursor a la proxima linea */
			offset = MAXLINEA - (tickpos % MAXLINEA);
			*pos += offset;
			break;
		case '\t':
			/* tabs de 4 espacios */
			offset = 8 - *pos % 8;
			*pos += offset;
			break;
		case '\b':
			*pos -= 2;
			donde[*pos] = 0;
			break;
		default:
			donde[*pos] = desde[j];
			*pos += 2;
			break;
		}
	}

	if (terminalActual == temp->terminal)
		update_cursor(*pos / 160, (*pos % 160) / 2);
}

char *
int_86(int codigo, void *param1, void *param2) {
	switch (codigo) {
	case READ_CODE:
		return (char *) GetChar();
		break;
	case MALLOC_CODE:
		return (char *) processMalloc((int) param1);
		break;
	case RENICE_CODE:
		Renice((int) param1, (int) param2);
		break;
	case 2: /*kill*/
			Kill((int) param1);
			break;
	default:
		break;
	}
}

void int_85(int codigo, void *parametro1, void *parametro2, void *parametro3) {
	switch (codigo) {
	case 1: /*exec*/
		exec(parametro1, parametro2, (int) parametro3);
		break;

	case 5: /*write*/
		escribir((char *) parametro1, (char *) parametro2, (int) parametro3);
		break;
	case 10:/*mkdir code*/
		mkdir((inode**) parametro1, (int*) parametro2, (char*) parametro3);
		break;
	case 12:/*cd code*/
		cd((inode**) parametro1, (int*) parametro2, (char*) parametro3);
		break;
	default:
		break;
	}
	return;
}

void seteaIDT(void) {
	setup_IDT_entry(&idt[0x08], 0x08, (dword) &int_08_hand, ACS_INT, 0);
	// CARGA DE IDT CON LA RUTINA DE ATENCION DE TECLADO
	setup_IDT_entry(&idt[0x09], 0x08, (dword) &int_09_hand, ACS_INT, 0);
	// Systems Interrupts
	setup_IDT_entry(&idt[0x85], 0x08, (dword) &int_85_hand, ACS_INT, 0);
	setup_IDT_entry(&idt[0x86], 0x08, (dword) &int_86_hand, ACS_INT, 0);
	// divide by zero
	setup_IDT_entry(&idt[0x00], 0x08, (dword) &div0_hand, ACS_INT, 0);
	// out of bounds
	setup_IDT_entry(&idt[0x05], 0x08, (dword) &bounds_hand, ACS_INT, 0);
	// opcode invalido
	setup_IDT_entry(&idt[0x06], 0x08, (dword) &opCode_hand, ACS_INT, 0);
	// segmento no presente
	setup_IDT_entry(&idt[0x0B], 0x08, (dword) &snoPresent_hand, ACS_INT, 0);
	// ay se me rompe el stack!
	setup_IDT_entry(&idt[0x0C], 0x08, (dword) &ssf_hand, ACS_INT, 0);
	// error de prot. general
	setup_IDT_entry(&idt[0x0D], 0x08, (dword) &generalPfault_hand, ACS_INT, 0);
	// saca la mano antonio de mi pagina
	setup_IDT_entry(&idt[0x0E], 0x08, (dword) &pageFault_hand, ACS_INT, 0);

	/* Carga de IDTR    */
	idtr.base = 0;
	idtr.base += (dword) &idt;
	idtr.limit = sizeof(idt) - 1;

	_lidt(&idtr);
}

void initPaging(void) {
	unsigned long address = 0; // holds the physical address of where a page is
	unsigned int i, j;

	// map the kernel pages and table directories and table pages zone (first 8MB)
	for (i = 0; i < 2; i++) {
		page_table = page_table + 4096;

		for (j = 0; j < 1024; j++) {
			// fill  the page directory
			page_table[j] = address | 3;
			// attribute set to: supervisor level, read/write, present(011 in binary)
			address = address + 4096;
		}

		page_directory[i] = (unsigned long) page_table;
		page_directory[i] = page_directory[i] | 3;

	}

	// map the rest of memory left with no present attribute, ¿¿¿exept FS???
	page_table = page_directory + 8192;

	for (i = 2; i < (int) /*maxmem/4096*/8; i++) {
		/*Dado que cada entrada de la tabla de paginas son de 32 bits, me corro
		 1024 lugares que fueron usados para escribir las siguientes entradas */
		page_table = page_table + 4096;

		for (j = 0; j < 1024; j++) {
			if (address < (unsigned long )fs || address > (unsigned long )(*(fs
					+ MAX_INODE - 1) + DEEP - 1) + sizeof(inode)) {
				page_table[j] = address & 0xFFFFFFFE;
				address = address + 4096;
			} else {
				page_table[j] = address | 0x1;
				address = address + 4096;
			}
		}

		page_directory[i] = (unsigned long) page_table;
		page_directory[i] = page_directory[i] & 0xFFFFFFFE;

	}
	// page var reload his initial value

	// write_cr3, read_cr3, write_cr0, and read_cr0 all come from the
	// assembly functions
	write_cr3(page_directory); // put that page directory address into CR3
	write_cr0(read_cr0() | 0x80000000); // set the paging bit in CR0 to 1
}

#define FILESYSSIZE (1024 * 2)
void *
allocateFS(void) {
	int i;
	fs = Malloc(MAX_INODE * sizeof(inode *));
	for (i = 0; i < MAX_INODE; i++) {
		*(fs + i) = Malloc(DEEP * sizeof(inode));
	}
	return fs;
	//	levantaPaginas((void *) (NULL + 1));
}


/**********************************************
 kmain()
 Punto de entrada de codigo C.
 *************************************************/

kmain(unsigned long infogrub, unsigned int magic) {
	int num, j, k;

	_Cli();

	/* Borra la pantalla. */
	k_clear_screen();

	seteaIDT();

	/* Habilito interrupcion de timer tick , teclado y COM */
	mascaraPIC1(IRQ0 & IRQ1 & IRQ3 & IRQ4);
	mascaraPIC2(0xFF);

	set_multiBoot(infogrub);
	/* cant. de memoria existente en kbytes */
	maxmem = getMemSize() + 1024;

	allocateFS();
	init_fs(fs);

	initializeFifos();

	initPaging();

	initScheduler();

	initVersus();

	for (k = 0; k < NTERMS; k++) {
		terminales[k].terminal = (void *) allocteTerminal(FILAS * COLS * 2);
		terminales[k].posicion = 0;
		terminales[k].writeBuf = terminales[k].buffer;
		terminales[k].readBuf = terminales[k].buffer;

		for (j = 0; j < COLS * 2; j++)
			terminales[k].buffer[j] = 0;
	}

	backup = terminales[0].terminal;
	keyboardBuffer = terminales[0].buffer;
	writeBuffer = terminales[0].writeBuf;
	readBuffer = terminales[0].readBuf;
	terminales[0].terminal = (char *) 0xb8000;

	// inicia un proceso shell en cada tty y a dios.

	CrearProceso("shell 0", shell, 0, 0, (char **) 0, 2, 0, 0x4000);
	CrearProceso("shell 1", shell, 1, 0, (char **) 0, 2, 0, 0x4000);
	CrearProceso("shell 2", shell, 2, 0, (char **) 0, 2, 0, 0x4000);
	CrearProceso("shell 3", shell, 3, 0, (char **) 0, 2, 0, 0x4000);
	CrearProceso("shell 4", shell, 4, 0, (char **) 0, 2, 0, 0x4000);
	CrearProceso("shell 5", shell, 5, 0, (char **) 0, 2, 0, 0x4000);
	CrearProceso("idle", idle, 0, 0, (char **) 0, 2, 0, 0x4000);
	/* Solo la tty 0 inicia como no bloqueada. */
	for (k = 2; k <= NTERMS; k++) {
		bloqueaProceso(k, 1);
	}
	desbloqueaProceso(IDLE);
	_Sti();

	while (1) {
	}

}
